package impl

import (
	"bytes"
	"code-sandbox/src/main/model/entity"
	"context"
	"encoding/json"
	"fmt"
	"github.com/bwmarrin/snowflake"
	_ "github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/image"
	"github.com/docker/docker/client"
	"github.com/docker/docker/pkg/stdcopy"
	"io"
	"os"
	"path/filepath"
	"time"
)

type SandBoxServiceImpl struct{}

var node *snowflake.Node

func init() {
	var err error
	node, err = snowflake.NewNode(1)
	if err != nil {
		panic("初始化雪花算法失败: " + err.Error())
	}
}

func (s *SandBoxServiceImpl) RunCode(questionSubmit *entity.QuestionSubmit) (*entity.QuestionSubmitResponse, error) {
	// 1. 创建基于雪花ID的临时目录结构
	baseDir := "./tmp"
	uniqueID := node.Generate().String()
	hostCodePath := filepath.Join(baseDir, uniqueID, "Main.go")
	if err := os.MkdirAll(filepath.Dir(hostCodePath), os.ModePerm); err != nil {
		return nil, fmt.Errorf("无法创建子目录: %v", err)
	}

	// 2. 获取用户提交的代码和判题用例
	code := questionSubmit.Code
	rawJudgeCase := questionSubmit.JudgeCase // string，JSON数组字符串

	// 3. 解析 JudgeCase JSON 字符串为 []string
	var judgeCase []string
	if rawJudgeCase != "" {
		if err := json.Unmarshal([]byte(rawJudgeCase), &judgeCase); err != nil {
			return nil, fmt.Errorf("解析 JudgeCase 失败: %v", err)
		}
	} else {
		judgeCase = []string{} // 默认空切片
	}

	// 4. 将代码写入宿主机
	if err := os.WriteFile(hostCodePath, []byte(code), 0644); err != nil {
		return nil, fmt.Errorf("无法保存代码文件: %v", err)
	}

	// 5. Docker 客户端
	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		return nil, err
	}
	defer cli.Close()

	ctx := context.Background()

	// 6. 检查镜像是否存在，不存在才拉取
	imageName := "golang:1.22"
	found, err := isImageExists(cli, ctx, imageName)
	if err != nil {
		return nil, err
	}
	if !found {
		fmt.Printf("镜像 %s 不存在，正在拉取...\n", imageName)
		if err := pullImage(cli, ctx, imageName); err != nil {
			return nil, err
		}
	} else {
		fmt.Println("镜像已存在，跳过拉取")
	}

	// 7. 构建容器命令：go run Main.go [case1 case2 ...]
	cmd := []string{"go", "run", "/go/src/Main.go"}
	for _, input := range judgeCase {
		cmd = append(cmd, input)
	}

	// 8. 创建容器配置
	config := &container.Config{
		Image: imageName,
		Cmd:   cmd,
		Tty:   false,
	}

	hostConfig := &container.HostConfig{
		Binds: []string{
			fmt.Sprintf("%s:/go/src/Main.go", hostCodePath),
		},
		Resources: container.Resources{
			Memory: 536870912, // 512MB 内存限制
		},
		NetworkMode: "none", // 禁止网络访问
	}

	// 9. 创建容器
	resp, err := cli.ContainerCreate(ctx, config, hostConfig, nil, nil, "")
	if err != nil {
		return nil, err
	}
	defer cli.ContainerRemove(ctx, resp.ID, container.RemoveOptions{})

	// 10. 启动容器并记录开始时间
	startTime := time.Now()
	if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
		return nil, err
	}

	// 11. 等待容器执行完成
	statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
	select {
	case err := <-errCh:
		if err != nil {
			return nil, err
		}
	case <-statusCh:
	}

	// 12. 获取容器日志输出
	out, err := cli.ContainerLogs(ctx, resp.ID, container.LogsOptions{ShowStdout: true, ShowStderr: true})
	if err != nil {
		return nil, err
	}
	var stdoutBuf, stderrBuf bytes.Buffer
	stdcopy.StdCopy(&stdoutBuf, &stderrBuf, out)

	// 13. 构造响应
	response := &entity.QuestionSubmitResponse{
		Output:       stdoutBuf.String(),
		ErrorMessage: stderrBuf.String(),
		ExecTimeMS:   int32(time.Since(startTime).Seconds()),
		MemUsageMB:   0,
		JudgeInfo:    questionSubmit.JudgeInfo,
	}

	// 14. 清理临时目录（可选）
	_ = os.RemoveAll(filepath.Dir(hostCodePath))

	return response, nil
}

// 判断镜像是否存在
func isImageExists(cli *client.Client, ctx context.Context, imageName string) (bool, error) {
	images, err := cli.ImageList(ctx, image.ListOptions{All: false})
	if err != nil {
		return false, err
	}
	for _, img := range images {
		for _, tag := range img.RepoTags {
			if tag == imageName {
				return true, nil
			}
		}
	}
	return false, nil
}

// 拉取镜像
func pullImage(cli *client.Client, ctx context.Context, imageName string) error {
	pullOut, err := cli.ImagePull(ctx, imageName, image.PullOptions{})
	if err != nil {
		return err
	}
	defer pullOut.Close()
	_, _ = io.Copy(io.Discard, pullOut) // 静默下载
	return nil
}
